package br.odb.portalizer;

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Vector;

import br.odb.libscene.Actor;
import br.odb.libscene.Mesh;
import br.odb.libscene.Sector;
import br.odb.libscene.World;
import br.odb.utils.Utils;

/**
 * 
 * @author Daniel "Monty" Monteiro
 * 
 */
public class WorldPartitioner {

	static boolean multipass = false;
	static boolean hollow = false;
	public static int transientStartSector;

	public static void secureLog(final String msg) {
		try {
			System.out.println(msg);
		} catch (final Throwable t) {

		}
	}

	/**
	 * 
	 */
	public static World optimize(final World world) {
		return optimize(world, false);
	}

	public static Sector split(final Sector sector, final Hyperplane hyperplane) {

		Sector toReturn;
		toReturn = null;

		if (hyperplane.getVector().getX() != Integer.MAX_VALUE) {
			// plane in YZ

			if (sector.getX0() < hyperplane.getVector().getX()
					&& hyperplane.getVector().getX() < sector.getX1()) {
				toReturn = new Sector(sector);
				toReturn.setX0(snap( hyperplane.getVector().getX() ) );
				sector.setX1(snap( hyperplane.getVector().getX() ) );
				sector.setIsMaster(false);
				toReturn.setIsMaster(false);
			}

		} else if (hyperplane.getVector().getY() != Integer.MAX_VALUE) {
			// plane in XZ
			if (sector.getY0() < hyperplane.getVector().getY()
					&& hyperplane.getVector().getY() < sector.getY1()) {
				toReturn = new Sector(sector);
				toReturn.setY0( snap( hyperplane.getVector().getY() ) );
				sector.setY1( snap( hyperplane.getVector().getY() ) );
				sector.setIsMaster(false);
				toReturn.setIsMaster(false);
			}

		} else if (hyperplane.getVector().getZ() != Integer.MAX_VALUE) {
			// plane in XY
			if (sector.getZ0() < hyperplane.getVector().getZ()
					&& hyperplane.getVector().getZ() < sector.getZ1()) {
				toReturn = new Sector(sector);
				toReturn.setZ0( snap( hyperplane.getVector().getZ() ) );
				sector.setZ1(snap( hyperplane.getVector().getZ()) );
				sector.setIsMaster(false);
				toReturn.setIsMaster(false);

			}

		}

		return toReturn;
	}

	private static float snap( float val ) {

//		if ( Mesh.snapBoundsToVertexes )
//			return Math.round( val );
//		else
			return val;
	}

	public static Hyperplane generateHyperplane(final Sector sector,
			final byte kind) {
		float n = 0.0f;

		switch (kind) {
		case Sector.FACE_N:
			n = sector.getZ0();
			break;
		case Sector.FACE_S:
			n = sector.getZ1();
			break;
		case Sector.FACE_W:
			n = sector.getX0();
			break;
		case Sector.FACE_E:
			n = sector.getX1();
			break;
		case Sector.FACE_FLOOR:
			n = sector.getY0();
			break;
		case Sector.FACE_CEILING:
			n = sector.getY1();
			break;
		}

		return new Hyperplane(kind, n, sector);
	}

	/**
	 * 
	 */
	public static World optimize(final World world, final boolean preserveIds) {

		ArrayList<Sector> workArea = new ArrayList<Sector>();
		World toReturn = new World();
		Sector sector;
		Sector current;
		Sector splitting;
		Hyperplane hyperplane;
		boolean changed = true;
		int originalId;
		long startTime;
		final ArrayList<Hyperplane> planes = new ArrayList<Hyperplane>();
		final ArrayList<Sector> toRemove = new ArrayList<Sector>();
		sector = null;
		
		//não existe world sem master sector. é lei.
		
		for (int c = 1; c < world.getTotalSectors(); ++c) {

			current = world.getSector(c);

			if (current != null && !current.isDegenerate()) {

				originalId = current.getId();

				workArea.add(current);

				if (preserveIds)
					current.setId(originalId);

			} else if (current != null && current.isDegenerate()) {
				secureLog("setor " + c + " is degenerated");
			}

		}

		int passes = 0;
		split_pass:
		while (changed) {
			changed = false;
			passes++;
			secureLog("pass: " + passes + " for " + workArea.size()
					+ " sector(s)");

			startTime = System.currentTimeMillis();
			planes.clear();
			
			for (int f = 0; f < workArea.size(); ++f) {

				current = workArea.get(f);
				current.setParent( current.getId() );
				secureLog( "parent " + current.getParent() );
				
				if (current != null && current.isDegenerate()) {
					workArea.remove( current );
					
					if  ( multipass )
						changed = true;
					
					continue split_pass;
				}

				for (byte d = 0; d < 6; ++d) {

					hyperplane = generateHyperplane(current, d);

					if (!planes.contains(hyperplane))
						planes.add(hyperplane);
				}
			}
			System.out.println( "gerar os hiperplanos levou " + ( ( System.currentTimeMillis() - startTime )  ) + " milisegundos" );
			
			for (int e = 0; e < workArea.size(); ++e) {

				splitting = workArea.get(e);

				for (int c = 0; c < planes.size(); ++c) {

					hyperplane = planes.get(c);

					if (hyperplane.getGenerator() == splitting)
						continue;

					sector = split(splitting, hyperplane);

					if (sector != null && !sector.isDegenerate()) {
						
						if  ( multipass )
							changed = true;

						if (!workArea.contains(sector))
							workArea.add(sector);

						if (preserveIds)
							sector.setId(splitting.getId());
					}
				}
			}

		}


		
		
		for (int z = 0; z < workArea.size(); ++z) {
			workArea.get(z).setId(z);
		}

		changed = true;
		check_pass:
		while (changed) {
			secureLog("checando...");
			changed = false;
			toRemove.clear();

			for (int z = 0; z < workArea.size(); ++z) {

				if (toRemove.contains(workArea.get(z)))
					continue;
				
				/*
				 * if ( world.getSector( z ).getDX() < 0.001f ) {
				 * System.out.println("setor menor que 0.001 em x: " + z );
				 * world.sectors.remove( z ); mudou = true; // z =
				 * world.getTotalSectors(); // continue; }
				 * 
				 * if ( world.getSector( z ).getDY() < 0.001f ) {
				 * System.out.println("setor menor que 0.001 em y: " + z );
				 * world.sectors.remove( z ); mudou = true; // z =
				 * world.getTotalSectors(); // continue; }
				 * 
				 * if ( world.getSector( z ).getDZ() < 0.001f ) {
				 * System.out.println("setor menor que 0.001 em z: " + z );
				 * world.sectors.remove( z ); mudou = true; // z =
				 * world.getTotalSectors(); // continue; }
				 */
				for (int w = z + 1; w < workArea.size(); ++w) {

					if (workArea.get(z).coincidant(workArea.get(w))) {

						if (!toRemove.contains(workArea.get(w)))
							toRemove.add(workArea.get(w));

//						secureLog("setores juntos: " + z + " e " + w);

						if  ( multipass )
							changed = true;


					} else {
						if (workArea.get(z).contains(
								workArea.get(w))) {
//							secureLog("setores se incluem: " + z + " e " + w);

							if  ( multipass )
								changed = true;
						}
					}

				}

				if (z < workArea.size()
						&& workArea.get(z).isSolid()) {
					secureLog("setor solido encontrado!");

					if  ( multipass )
						changed = true;

					if (!toRemove.contains(workArea.get(z)))
						toRemove.add(workArea.get(z));

					continue;
				}
			}
			secureLog("acabei?");

			if (toRemove.size() > 0)
				changed = true;

			for (int c = 0; c < toRemove.size(); ++c) {
				//secureLog("removing " + toRemove.get(c).getId() + "...");
				workArea.remove( toRemove.get(c) );
			}
		}

		secureLog(" finished!");
		secureLog(" generated " + workArea.size() + " sector(s)");

		toReturn = new World();
		toReturn.addSector( new Sector() );
		
		for ( int c = 0; c < workArea.size(); ++c ) {
			current = workArea.get( c );
			current.setId( c + 1 );
			
			if ( WorldPartitioner.transientStartSector != -1 ) {
				
				secureLog( "currentParent " + current.getParent() + " / " + WorldPartitioner.transientStartSector );
				
				if ( current.getParent() == WorldPartitioner.transientStartSector ) {
					
					WorldPartitioner.transientStartSector = current.getId();
					secureLog( "starting at sector " + current.getId() );
				}
			}
			
			toReturn.addSector( current );
		}
		
		return toReturn;
	}

	public static void dumpTopViewToSVG(final World world, final String filename) {
		Sector sector;

		try {
			final FileOutputStream fos = new FileOutputStream(filename);
			final BufferedWriter bw = new BufferedWriter(
					new OutputStreamWriter(fos));

			bw.write("<?xml version='1.0' encoding='UTF-8' standalone='no'?>");
			bw.newLine();
			bw.write("<svg>");
			bw.newLine();
			bw.write("<g>");
			bw.newLine();

			for (int c = 0; c < world.getTotalSectors(); ++c) {

				sector = world.getSector(c);
				bw.write(sector.toSVGTopView());
				bw.newLine();
			}

			bw.write("</g>");
			bw.newLine();
			bw.write("</svg>");
			bw.newLine();
			bw.close();

		} catch (final Exception e) {
			e.printStackTrace();
		}
	}

	public static void buildConvexHulls(final World world,
			final ArrayList<Mesh> meshes) {
		Mesh mesh;
		world.addSector(new Sector(0.0f, 255.0f, 0.0f, 255.0f, 0.0f, 255.0f));

		for (int c = 0; c < meshes.size(); ++c) {
			mesh = meshes.get(c);
			world.addSector(mesh.getConvexHull());
		}
	}

	

	public static void guessLinks(final World world, boolean guessDoors ) {

		Sector s1;
		Sector s2;

		for (int c = 1; c < world.getTotalSectors(); ++c) {
			s1 = world.getSector(c);
			s1.setId(c);
		}

		
		for (int c = 1; c < world.getTotalSectors(); ++c) {

			s1 = world.getSector(c);


			for (int d = 1; d < world.getTotalSectors(); ++d) {

				if (c == d)
					continue;

				s2 = world.getSector(d);

				if (s2.isSolid())
					continue;

				if (Utils.eqFloat(s1.getX0(), s2.getX1())
						&& Utils.eqFloat(s1.getZ0(), s2.getZ0())
						&& Utils.eqFloat(s1.getZ1(), s2.getZ1())
						&& Utils.eqFloat(s1.getY0(), s2.getY0())
						&& Utils.eqFloat(s1.getY1(), s2.getY1())) {
					s1.setLink(Sector.FACE_W, s2.getId());
					s2.setLink(Sector.FACE_E, s1.getId());
				}

				if (Utils.eqFloat(s1.getX1(), s2.getX0())
						&& Utils.eqFloat(s1.getZ0(), s2.getZ0())
						&& Utils.eqFloat(s1.getZ1(), s2.getZ1())
						&& Utils.eqFloat(s1.getY0(), s2.getY0())
						&& Utils.eqFloat(s1.getY1(), s2.getY1())) {
					s1.setLink(Sector.FACE_E, s2.getId());
					s2.setLink(Sector.FACE_W, s1.getId());
				}

				if (Utils.eqFloat(s1.getY0(), s2.getY1())
						&& Utils.eqFloat(s1.getZ0(), s2.getZ0())
						&& Utils.eqFloat(s1.getZ1(), s2.getZ1())
						&& Utils.eqFloat(s1.getX0(), s2.getX0())
						&& Utils.eqFloat(s1.getX1(), s2.getX1())) {
					s1.setLink(Sector.FACE_FLOOR, s2.getId());
					s2.setLink(Sector.FACE_CEILING, s1.getId());
				}

				if (Utils.eqFloat(s1.getY1(), s2.getY0())
						&& Utils.eqFloat(s1.getZ0(), s2.getZ0())
						&& Utils.eqFloat(s1.getZ1(), s2.getZ1())
						&& Utils.eqFloat(s1.getX0(), s2.getX0())
						&& Utils.eqFloat(s1.getX1(), s2.getX1())) {
					s1.setLink(Sector.FACE_CEILING, s2.getId());
					s2.setLink(Sector.FACE_FLOOR, s1.getId());
				}

				if (Utils.eqFloat(s1.getZ0(), s2.getZ1())
						&& Utils.eqFloat(s1.getX0(), s2.getX0())
						&& Utils.eqFloat(s1.getX1(), s2.getX1())
						&& Utils.eqFloat(s1.getY0(), s2.getY0())
						&& Utils.eqFloat(s1.getY1(), s2.getY1())) {
					s1.setLink(Sector.FACE_N, s2.getId());
					s2.setLink(Sector.FACE_S, s1.getId());
				}

				if (Utils.eqFloat(s1.getZ1(), s2.getZ0())
						&& Utils.eqFloat(s1.getX0(), s2.getX0())
						&& Utils.eqFloat(s1.getX1(), s2.getX1())
						&& Utils.eqFloat(s1.getY0(), s2.getY0())
						&& Utils.eqFloat(s1.getY1(), s2.getY1())) {
					s1.setLink(Sector.FACE_S, s2.getId());
					s2.setLink(Sector.FACE_N, s1.getId());
				}
			}
		}


//		if ( guessDoors ) {
//			for (int c = 0; c < world.getTotalSectors(); ++c) {
//	
//				s1 = world.getSector(c);
//	
//				for (int d = 0; d < 6; ++d) {
//					try {
//	
//						if (s1.getDoor(d) != null) {
//	
//							if (s1.getLink(Utils.getLeft(d)) != 0) {
//								s2 = world.getSector(s1.getLink(Utils.getLeft(d)));
//	
//								if (s2.getLink(d) != 0)
//									s2.setDoorAt(d, s2.getLink(d));
//							}
//	
//							if (s1.getLink(Utils.getRight(d)) != 0) {
//								s2 = world.getSector(s1.getLink(Utils.getRight(d)));
//	
//								if (s2.getLink(d) != 0)
//									s2.setDoorAt(d, s2.getLink(d));
//							}
//	
//						}
//					} catch (final InvalidSlotException e) {
//						// TODO Auto-generated catch block
//						e.printStackTrace();
//					}
//	
//				}
//			}
//		}
	}

	public static void renewIds(final World world,
			final boolean preserveNegatives) {
		for (int c = 0; c < world.getTotalSectors(); ++c) {
			if (!preserveNegatives || world.getSector(c).getId() >= 0) {
				world.getSector(c).setId(c);
			}
		}
	}

	public static void removeNegatives(final World world) {
		for (int c = 0; c < world.getTotalSectors(); ++c) {
			if (world.getSector(c).getId() < 0) {
				world.removeSector(c, false);
				c -= 2;
			}
		}
	}

	public static void deleteHollowSectors(World world) {
		Vector< Sector > toDelete = new Vector<Sector>();
		
		for (int c = 1; c < world.getTotalSectors(); ++c) {
			if (world.getSector(c).getVisibleFacesCount() == 0) {
				toDelete.add( world.getSector(c) );			
			}
		}
		
		for ( int c = 0; c < toDelete.size(); ++c )
			world.removeSector( toDelete.get( c ) );
	}
}
